package utils

import (
	"regexp"
	"strings"
	"sync"
)

const (
	DEFAULT_PATH_SEPARATOR  = "/"
	CACHE_TURNOFF_THRESHOLD = 65536
	WILDCARD_CHARS          = "*?:"
)

type AntPathMatcher struct {
	pathSeparator         string
	caseSensitive         bool //true
	trimTokens            bool //false
	cachePatterns         bool
	tokenizedPatternCache map[string][]string
	stringMatcherCache    map[string]*AntPathStringMatcher
}

var antPathMatcher *AntPathMatcher
var onceAntPathMatcher sync.Once

func AntPathMatcherInstance() *AntPathMatcher {
	onceAntPathMatcher.Do(func() {
		antPathMatcher = &AntPathMatcher{pathSeparator: DEFAULT_PATH_SEPARATOR, caseSensitive: true,
			trimTokens: false, cachePatterns: true, tokenizedPatternCache: make(map[string][]string, 256),
			stringMatcherCache: make(map[string]*AntPathStringMatcher, 256)}
	})
	return antPathMatcher
}

func (m *AntPathMatcher) deactivatePatternCache() {
	m.cachePatterns = false
	m.tokenizedPatternCache = make(map[string][]string, 256)
	m.stringMatcherCache = make(map[string]*AntPathStringMatcher, 256)
}

func (m *AntPathMatcher) DoMatch(pattern string, path string, uriTemplateVariables map[string]string) bool {
	if strings.HasPrefix(path, m.pathSeparator) != strings.HasPrefix(pattern, m.pathSeparator) {
		return false
	}

	pattDirs := m.tokenizePattern(pattern)
	if m.caseSensitive && !m.isPotentialMatch(path, pattDirs) {
		return false
	}

	pathDirs := m.tokenizePath(path)
	pattIdxStart := 0
	pattIdxEnd := len(pattDirs) - 1
	pathIdxStart := 0
	pathIdxEnd := len(pathDirs) - 1

	// Match all elements up to the first **
	for pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd {
		pattDir := pattDirs[pattIdxStart]
		if "**" == pattDir {
			break
		}
		if !m.matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables) {
			return false
		}
		pattIdxStart++
		pathIdxStart++
	}

	if pathIdxStart > pathIdxEnd {
		// Path is exhausted, only match if rest of pattern is * or **'s
		if pattIdxStart > pattIdxEnd {
			return strings.HasSuffix(pattern, m.pathSeparator) == strings.HasSuffix(path, m.pathSeparator)
		}

		if pattIdxStart == pattIdxEnd && "*" == pattDirs[pattIdxStart] && strings.HasSuffix(path, m.pathSeparator) {
			return true
		}
		for i := pattIdxStart; i <= pattIdxEnd; i++ {
			if "**" != pattDirs[i] {
				return false
			}
		}
		return true
	} else if pattIdxStart > pattIdxEnd {
		// String not exhausted, but pattern is. Failure.
		return false
	}

	// up to last '**'
	for pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd {
		pattDir := pattDirs[pattIdxEnd]
		if "**" == pattDir {
			break
		}
		if !m.matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables) {
			return false
		}
		pattIdxEnd--
		pathIdxEnd--
	}
	if pathIdxStart > pathIdxEnd {
		// String is exhausted
		for i := pattIdxStart; i <= pattIdxEnd; i++ {
			if "**" != pattDirs[i] {
				return false
			}
		}
		return true
	}

	for pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd {
		patIdxTmp := -1
		for i := pattIdxStart + 1; i <= pattIdxEnd; i++ {
			if "**" == pattDirs[i] {
				patIdxTmp = i
				break
			}
		}
		if patIdxTmp == pattIdxStart+1 {
			// '**/**' situation, so skip one
			pattIdxStart++
			continue
		}
		// Find the pattern between padIdxStart & padIdxTmp in str between
		// strIdxStart & strIdxEnd
		patLength := patIdxTmp - pattIdxStart - 1
		strLength := pathIdxEnd - pathIdxStart + 1
		foundIdx := -1

	strLoop:
		for i := 0; i <= strLength-patLength; i++ {
			for j := 0; j < patLength; j++ {
				subPat := pattDirs[pattIdxStart+j+1]
				subStr := pathDirs[pathIdxStart+i+j]
				if !m.matchStrings(subPat, subStr, uriTemplateVariables) {
					continue strLoop
				}
			}
			foundIdx = pathIdxStart + i
			break
		}

		if foundIdx == -1 {
			return false
		}

		pattIdxStart = patIdxTmp
		pathIdxStart = foundIdx + patLength
	}

	for i := pattIdxStart; i <= pattIdxEnd; i++ {
		if "**" != pattDirs[i] {
			return false
		}
	}

	return true
}

func (m *AntPathMatcher) isPotentialMatch(path string, pattDirs []string) bool {
	if !m.trimTokens {
		pos := 0
		for _, pattDir := range pattDirs {
			skipped := m.skipSeparator(path, pos, m.pathSeparator)
			pos += skipped
			skipped = m.skipSegment(path, pos, pattDir)
			if skipped < len(pattDir) {
				return skipped > 0 || (len(pattDir) > 0 && m.isWildcardChar(pattDir[0:1]))
			}
			pos += skipped
		}
	}
	return true
}

func (m *AntPathMatcher) skipSegment(path string, pos int, prefix string) int {
	skipped := 0
	for i := 0; i < len(prefix); i++ {
		c := prefix[i : i+1]
		if m.isWildcardChar(c) {
			return skipped
		}
		currPos := pos + skipped
		if currPos >= len(path) {
			return 0
		}
		if c == path[currPos:currPos+1] {
			skipped++
		}
	}
	return skipped
}

func (m *AntPathMatcher) skipSeparator(path string, pos int, separator string) int {
	skipped := 0
	for strings.HasPrefix(path[pos+skipped:], separator) {
		skipped += len(separator)
	}
	return skipped
}

func (m *AntPathMatcher) isWildcardChar(c string) bool {
	for i := 0; i < len(WILDCARD_CHARS); i++ {
		if c == WILDCARD_CHARS[i:i+1] {
			return true
		}
	}
	return false
}

func (m *AntPathMatcher) tokenizePattern(pattern string) []string {
	tokenized := make([]string, 0)
	ok := false
	cachePatterns := m.cachePatterns
	if cachePatterns {
		tokenized, ok = m.tokenizedPatternCache[pattern]
	}
	if !ok {
		tokenized = m.tokenizePath(pattern)
		if cachePatterns && len(m.tokenizedPatternCache) >= CACHE_TURNOFF_THRESHOLD {
			// Try to adapt to the runtime situation that we're encountering:
			// There are obviously too many different patterns coming in here...
			// So let's turn off the cache since the patterns are unlikely to be reoccurring.
			m.deactivatePatternCache()
			return tokenized
		}
		if cachePatterns {
			m.tokenizedPatternCache[pattern] = tokenized
		}
	}
	return tokenized
}

func (m *AntPathMatcher) tokenizePath(path string) []string {
	st := strings.Split(path, m.pathSeparator)
	tokens := make([]string, 0)
	for _, token := range st {
		if m.trimTokens {
			token = strings.TrimSpace(token)
		}
		if len(token) > 0 {
			tokens = append(tokens, token)
		}
	}
	return tokens
}

func (m *AntPathMatcher) matchStrings(pattern string, str string, uriTemplateVariables map[string]string) bool {
	r := m.getStringMatcher(pattern).MatchStrings(str, uriTemplateVariables)
	return r
}

func (m *AntPathMatcher) getStringMatcher(pattern string) *AntPathStringMatcher {
	matcher := new(AntPathStringMatcher)
	ok := false
	cachePatterns := m.cachePatterns
	if cachePatterns {
		matcher, ok = m.stringMatcherCache[pattern]
	}
	if !ok {
		matcher = NewAntPathStringMatcher(pattern)
		if cachePatterns && len(m.stringMatcherCache) >= CACHE_TURNOFF_THRESHOLD {
			// Try to adapt to the runtime situation that we're encountering:
			// There are obviously too many different patterns coming in here...
			// So let's turn off the cache since the patterns are unlikely to be reoccurring.
			m.deactivatePatternCache()
			return matcher
		}
		if cachePatterns {
			m.stringMatcherCache[pattern] = matcher
		}
	}
	return matcher
}

type AntPathStringMatcher struct {
	pattern       *regexp.Regexp
	patternString string
}

func NewAntPathStringMatcher(pattern string) *AntPathStringMatcher {
	regexStr := strings.ReplaceAll(pattern, "?", "(.)")
	regexStr = strings.ReplaceAll(regexStr, "*", "(.*)")
	regex := regexp.MustCompile(regexStr)
	antPathStringMatcher := &AntPathStringMatcher{pattern: regex, patternString: pattern}
	return antPathStringMatcher
}

func (m *AntPathStringMatcher) MatchStrings(str string, uriTemplateVariables map[string]string) bool {
	if strings.HasPrefix(m.patternString, ":") {
		if uriTemplateVariables != nil {
			uriTemplateVariables[m.patternString] = str
		}
		return true
	} else if !strings.Contains(m.patternString, "*") && !strings.Contains(m.patternString, "?") {
		return m.patternString == str
	}

	mstr := m.pattern.FindAllString(str, -1)
	if len(mstr) > 0 {
		return mstr[0] == str
	}
	return false
}
